package com.gframework.mybatis.dao;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Lang;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.reflection.ReflectionException;
import org.springframework.lang.Nullable;

import com.gframework.mybatis.dao.mybatis.provider.core.ProviderPlusLanguageDriver;
import com.gframework.mybatis.dao.mybatis.provider.per.ViewDaoProvider;
import com.gframework.sqlparam.Param;
import com.gframework.sqlparam.SqlParam;
import com.gframework.util.GUtils;
import com.github.pagehelper.Page;
import com.github.pagehelper.page.PageMethod;

/**
 * 视图dao操作接口.
 * 此接口定义了视图相关的操作方法（只包含查询，而且无主键操作）
 * 
 * @since 1.0.0
 * @author Ghwolf
 * @param <T> 表对应实体类的类型 {@link Serializable}接口子类，必须是一个POJO类型.
 * 
 * @see IPrimaryDAO
 * @see ITableBasicDAO
 */
public interface IViewDao<T extends Serializable> extends IMybatisDAO<T> {

	/**
	 * 查询全部数据，此操作慎用，对性能影响较大.
	 * 
	 * @return 查询结果封装成List集合返回
	 */
	@SelectProvider(ViewDaoProvider.class)
	@Lang(ProviderPlusLanguageDriver.class)
	public List<T> findAll();
	
	/**
	 * 根据自定义查询条件进行数据查询操作.
	 * <p>注意，虽然可以传null，或无实际条件的SqlParam对象，但是这样依赖就等同于执行{@link #findAll()}
	 * 
	 * @param param 自定义查询条件封装对象
	 * @return 查询结果封装成List集合返回
	 * @see SqlParam
	 */
	@SelectProvider(ViewDaoProvider.class)
	@Lang(ProviderPlusLanguageDriver.class)
	public List<T> find(@Nullable SqlParam param);

	/**
	 * 根据自定义查询条件，取得查询数据总数.
	 * 
	 * @param param 自定义查询条件封装对象
	 * @return 返回数据总数，如果没有返回0
	 * @see SqlParam
	 */
	@SelectProvider(ViewDaoProvider.class)
	@Lang(ProviderPlusLanguageDriver.class)
	public long count(@Nullable SqlParam param);
	
	/**
	 * 根据一个查询条件，取得查询数据总数.
	 * 
	 * @param param 一个条件
	 * @return 返回数据总数，如果没有返回0
	 * @see Param
	 */
	@SelectProvider(ViewDaoProvider.class)
	@Lang(ProviderPlusLanguageDriver.class)
	public long countSingleCondition(@Nullable Param param);
	
	/**
	 * 根据指定的返回结果类型来构建select查询子句，并返回仅type中包含的字段.
	 * <p>要求该字段必须要么是public的，要么提供了setter方法。
	 * <p>本方法并不能直接将结果转换为bean返回，这是因为受到mybatis的功能限制，返回的集合map字段的key是列名称而非属性名。
	 * 如果你希望能够直接返回bean，可以调用{@link #list(SqlParam, Class)} 方法。
	 * @param param 查询条件
	 * @param type 要转换的bean类型
	 * @return 将结果以List&lt;Map&gt;的形式返回。如果没有数据，则集合长度为0
	 */
	@SelectProvider(ViewDaoProvider.class)
	@Lang(ProviderPlusLanguageDriver.class)
	public List<Map<String,Object>> listByType(@Nullable SqlParam param,Class<?> type) ;
	
	/**
	 * 根据指定的一个条件和返回结果类型来构建select查询子句，并返回仅type中包含的字段.
	 * <p>要求该字段必须要么是public的，要么提供了setter方法。
	 * <p>本方法并不能直接将结果转换为bean返回，这是因为受到mybatis的功能限制，返回的集合map字段的key是列名称而非属性名。
	 * 如果你希望能够直接返回bean，可以调用{@link #list(Param, Class)} 方法。
	 * @param param 查询条件
	 * @param type 要转换的bean类型
	 * @return 将结果以List&lt;Map&gt;的形式返回。如果没有数据，则集合长度为0
	 */
	@SelectProvider(ViewDaoProvider.class)
	@Lang(ProviderPlusLanguageDriver.class)
	public List<Map<String,Object>> listByTypeCondition(@Nullable Param param,Class<?> type) ;
	
	/**
	 * 根据自定义查询条件，只查询某一字符串列的数据
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据集合，如果没有择机和长度为0
	 */
	@SelectProvider(value=ViewDaoProvider.class,method="listOneColumn")
	@Lang(ProviderPlusLanguageDriver.class)
	public List<String> listString(@Nullable SqlParam param,String column) ;
	
	/**
	 * 根据自定义查询条件，只查询某一Long类型列的数据
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据集合，如果没有择机和长度为0
	 */
	@SelectProvider(value=ViewDaoProvider.class,method="listOneColumn")
	@Lang(ProviderPlusLanguageDriver.class)
	public List<Long> listLong(@Nullable SqlParam param,String column) ;
	
	/**
	 * 根据自定义查询条件，只查询某一int类型列的数据
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据集合，如果没有择机和长度为0
	 */
	@SelectProvider(value=ViewDaoProvider.class,method="listOneColumn")
	@Lang(ProviderPlusLanguageDriver.class)
	public List<Integer> listInt(@Nullable SqlParam param,String column) ;
	
	/**
	 * 根据自定义查询条件，只查询某一double类型列的数据
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据集合，如果没有择机和长度为0
	 */
	@SelectProvider(value=ViewDaoProvider.class,method="listOneColumn")
	@Lang(ProviderPlusLanguageDriver.class)
	public List<Double> listDouble(@Nullable SqlParam param,String column) ;
	
	/**
	 * 根据自定义查询条件，只查询某一date类型列的数据
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据集合，如果没有择机和长度为0
	 */
	@SelectProvider(value=ViewDaoProvider.class,method="listOneColumn")
	@Lang(ProviderPlusLanguageDriver.class)
	public List<Date> listDate(@Nullable SqlParam param,String column) ;
	
	/**
	 * 根据自定义查询条件，只查询某一BigDecimal类型列的数据
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据集合，如果没有择机和长度为0
	 */
	@SelectProvider(value=ViewDaoProvider.class,method="listOneColumn")
	@Lang(ProviderPlusLanguageDriver.class)
	public List<BigDecimal> listBigDecimal(@Nullable SqlParam param,String column) ;
	
	/**
	 * 根据自定义查询条件，只查询某一BigInteger类型列的数据
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据集合，如果没有择机和长度为0
	 */
	@SelectProvider(value=ViewDaoProvider.class,method="listOneColumn")
	@Lang(ProviderPlusLanguageDriver.class)
	public List<BigInteger> listBigInteger(@Nullable SqlParam param,String column) ;
	
	/**
	 * 根据自定义查询条件，只查询某一字符串列的数据，第一个内容
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据，没有返回null
	 */
	default String getString(@Nullable SqlParam param,String column) {
		Page<String> page = PageMethod.offsetPage(0,1,false);
		this.listString(param, column);
		return page.isEmpty() ? null : page.get(0);
	} 
	
	/**
	 * 根据自定义查询条件，只查询某一Long类型列的数据，第一个内容
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据，没有返回null
	 */
	default Long getLong(@Nullable SqlParam param,String column)  {
		Page<Long> page = PageMethod.offsetPage(0,1,false);
		this.listLong(param, column);
		return page.isEmpty() ? null : page.get(0);
	}
	
	/**
	 * 根据自定义查询条件，只查询某一int类型列的数据，第一个内容
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据，没有返回null
	 */
	default Integer getInt(@Nullable SqlParam param,String column)  {
		Page<Integer> page = PageMethod.offsetPage(0,1,false);
		this.listInt(param, column);
		return page.isEmpty() ? null : page.get(0);
	}
	
	/**
	 * 根据自定义查询条件，只查询某一double类型列的数据，第一个内容
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据，没有返回null
	 */
	default Double getDouble(@Nullable SqlParam param,String column)  {
		Page<Double> page = PageMethod.offsetPage(0,1,false);
		this.listDouble(param, column);
		return page.isEmpty() ? null : page.get(0);
	}
	
	/**
	 * 根据自定义查询条件，只查询某一date类型列的数据，第一个内容
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据，没有返回null
	 */
	default Date getDate(@Nullable SqlParam param,String column)  {
		Page<Date> page = PageMethod.offsetPage(0,1,false);
		this.listDate(param, column);
		return page.isEmpty() ? null : page.get(0);
	}
	
	/**
	 * 根据自定义查询条件，只查询某一BigDecimal类型列的数据，第一个内容
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据，没有返回null
	 */
	default BigDecimal getBigDecimal(@Nullable SqlParam param,String column)  {
		Page<BigDecimal> page = PageMethod.offsetPage(0,1,false);
		this.listBigDecimal(param, column);
		return page.isEmpty() ? null : page.get(0);
	}
	
	/**
	 * 根据自定义查询条件，只查询某一BigInteger类型列的数据，第一个内容
	 * @param param 查询条件
	 * @param column 要查询的列
	 * @return 返回数据，没有返回null
	 */
	default BigInteger getBigInteger(@Nullable SqlParam param,String column) {
		Page<BigInteger> page = PageMethod.offsetPage(0,1,false);
		this.listBigInteger(param, column);
		return page.isEmpty() ? null : page.get(0);
	}

	/**
	 * 如果你认为你的查询操作只会返回一个结果，那么可以使用此方法，只返回一个结果或null而非集合.
	 * <p>需要注意的是，此方法内部不会对sql进行筛选加强，依然需要你使用分页插件进行筛选。此方法会调用{@link #list(SqlParam, Class)}方法，然后判断
	 * 集合是否有至少一个值，如果有，则返回集合中的第一个值，否则返回null。
	 * @param param 查询条件
	 * @param type 要转换的bean类型，必须是一个bean而非基本数据类型
	 * @return 返回查询得第一个结果，没有返回null
	 * @throws ReflectionException 反射异常，如没有提供无参构造，或构造方法出现异常或者不允许使用反射。
	 * 
	 * @see #list(SqlParam, Class)
	 */
	@Nullable
	default <R> R getOne(@Nullable SqlParam param,Class<R> type) {
		return GUtils.getFirst(this.list(param, type));
	}
	
	/**
	 * 如果你认为你的查询操作只会返回一个结果，那么可以使用此方法，只返回一个结果或null而非集合.
	 * <p>需要注意的是，此方法内部不会对sql进行筛选加强，依然需要你使用分页插件进行筛选。此方法会调用{@link #list(Param, Class)}方法，然后判断
	 * 集合是否有至少一个值，如果有，则返回集合中的第一个值，否则返回null。
	 * @param param 查询条件
	 * @param type 要转换的bean类型，必须是一个bean而非基本数据类型
	 * @return 返回查询得第一个结果，没有返回null
	 * @throws ReflectionException 反射异常，如没有提供无参构造，或构造方法出现异常或者不允许使用反射。
	 * 
	 * @see #list(Param, Class)
	 */
	@Nullable
	default <R> R getOne(@Nullable Param param,Class<R> type) {
		return GUtils.getFirst(this.list(param, type));
	}
	
	/**
	 * 根据指定查询条件以及返回结果类型进行查询，并将结果封装为type类型的对象集合返回.
	 * <p>该类型必须是一个简单的javabean类型，提供有无参构造和setter、getter方法，或者属性均为public的。
	 * @param param 查询条件
	 * @param type 要转换的bean类型
	 * @return 返回List集合，如果没有结果，则集合长度为0
	 * @throws ReflectionException 反射异常，如没有提供无参构造，或构造方法出现异常或者不允许使用反射。
	 */
	default <R> List<R> list(@Nullable SqlParam param,Class<R> type) {
		// 解决pageHelper冲突的问题
		Page<R> page = PageMethod.getLocalPage();
		List<Map<String,Object>> list = this.listByType(param, type);
		List<R> value = SearchByTypeUtil.list(list, type);
		if (page != null) {
			page.clear();
			page.addAll(value);
		}
		return value ;
	}
	
	/**
	 * 根据指定一个查询条件以及返回结果类型进行查询，并将结果封装为type类型的对象集合返回.
	 * <p>该类型必须是一个简单的javabean类型，提供有无参构造和setter、getter方法，或者属性均为public的。
	 * @param param 查询条件
	 * @param type 要转换的bean类型
	 * @return 返回List集合，如果没有结果，则集合长度为0
	 * @throws ReflectionException 反射异常，如没有提供无参构造，或构造方法出现异常或者不允许使用反射。
	 */
	default <R> List<R> list(@Nullable Param param,Class<R> type) {
		// 解决pageHelper冲突的问题
		Page<R> page = PageMethod.getLocalPage();
		List<Map<String,Object>> list = this.listByTypeCondition(param, type);
		List<R> value = SearchByTypeUtil.list(list, type);
		if (page != null) {
			page.clear();
			page.addAll(value);
		}
		return value ;
	}
	
}
